#include "Log.h"
#include <time.h>
#include <stdio.h>
#include <assert.h>
#include <sys/stat.h> // for stat
#if defined(_WIN32) || defined(_WIN64)
#else
#  include <unistd.h> // for unlink
#  include <sys/time.h> // for gettimeofday
#  include <string.h> // for strcpy
#endif
#include "DSTHMutex.h"

namespace dsth {

Logger* kLogger;
static Logger::LogLevel kLevel = Logger::E_ERROR;
long kMaxLogSize = 1024 * 1024 * 2;
int kMaxRotate = 6;

// below code mostly copy from Google leveldb
class Logger_Simple : public Logger {
public:
    Logger_Simple(FILE* file, const char* filename) : file_(file), rotate_(0) {
        strcpy(filename_, filename);
        InitRotate();
    }
    virtual ~Logger_Simple() { fclose(file_); }
    virtual void Logv(const char* prestr, const char* format, va_list ap) {
        //get thread id
        char buffer[500];
        for (int iter = 0; iter < 2; iter++) {
            char* base;
            int bufsize;
            if (iter == 0) {
                bufsize = sizeof(buffer);
                base = buffer;
            } else {
                bufsize = 30000;
                base = new char[bufsize];
            }
            char* p = base;
            char* limit = base + bufsize;

#if defined(_WIN32) || defined(_WIN64)
            SYSTEMTIME wtm;
            GetLocalTime(&wtm);
            p += _snprintf(p, limit - p,
                        "%04d/%02d/%02d-%02d:%02d:%02d.%06d %06llx %5s ",
                        wtm.wYear,
                        wtm.wMonth - 1,
                        wtm.wDay,
                        wtm.wHour,
                        wtm.wMinute,
                        wtm.wSecond,
                        static_cast<int>(wtm.wMilliseconds * 1000),
                        static_cast<long long unsigned int>(::GetCurrentThreadId()),
                        prestr);
#else // POSIX
            struct timeval now_tv;
            gettimeofday(&now_tv, NULL);
            const time_t seconds = now_tv.tv_sec;
		    struct tm t;
            localtime_r(&seconds, &t);
            p += snprintf(p, limit - p,
                    "%04d/%02d/%02d-%02d:%02d:%02d.%06d %llx %5s ",
                    t.tm_year + 1900,
                    t.tm_mon + 1,
                    t.tm_mday,
                    t.tm_hour,
                    t.tm_min,
                    t.tm_sec,
                    static_cast<int>(now_tv.tv_usec),
                    static_cast<long long unsigned int>(::pthread_self()),
                    prestr);
#endif
            // Print the message
            if (p < limit) {
                va_list backup_ap = ap;
                //va_copy(backup_ap, ap);
                p += vsnprintf(p, limit - p, format, backup_ap);
                va_end(backup_ap);
            }

            // Truncate to available space if necessary
            if (p >= limit) {
                if (iter == 0) {
                continue;       // Try again with larger buffer
             } else {
                p = limit - 1;
             }
          } 

          // Add newline if necessary
          if (p == base || p[-1] != '\n') {
              *p++ = '\n';
          }

          assert(p <= limit);
          {
              MutexLock guard(&mutex_);

              size_t wrote = fwrite(base, 1, p - base, file_);
              if (wrote != static_cast<size_t>(p - base) && (p - base) != 0) {
                  fprintf(stderr, "fwrite failed. %d %d\n", wrote, p - base );
              }
              fflush(file_);
              if (base != buffer) {
                  delete[] base;
              }
              long pos = ftell(file_); // ftell() better than stat()
              if (pos < kMaxLogSize) return;
              char new_file_name[256];
              sprintf(new_file_name, "%s.%d.bak", filename_, rotate_++);
              fclose(file_);
              unlink(new_file_name);
              if (0 != ::rename(filename_, new_file_name)) {
                fprintf(stderr, "rename failed. Rotate:%d\n", rotate_ );
              }

              file_ = fopen(filename_, "w");
              setvbuf(file_, NULL, _IONBF, 0);
              if (rotate_ == kMaxRotate) {
                rotate_ = 0;
              }
          }
          break;
        }
    }
private:
    void InitRotate() {
        char bak_file[256];
        int index = 0;
        time_t atime;
        for (int i = 0; i < kMaxRotate; ++i) {
            sprintf(bak_file, "%s.%d.bak", filename_, i);
            struct stat finfo;
            if (0 == ::stat(bak_file, &finfo)) {
                if (i == 0) {
                    atime = finfo.st_atime;
                }
                else if (atime > finfo.st_atime) {
                    index = i;
                }
            }
            else {
                index = i;
            }
        }
        rotate_ = index;
    }

    char filename_[256]; 
    Mutex mutex_; // guard file_ when rotate
    FILE* file_;
    int rotate_;
};

void Log(Logger::LogLevel level, const char* prestr, const char* format, ...) {
    if (kLevel > level) return;
    if (kLogger != NULL) {
        va_list ap;
        va_start(ap, format);
        kLogger->Logv(prestr, format, ap);
        va_end(ap);
    }
}

void NewLogger(const char* file, Logger::LogLevel level, int log_size_m, int rotate) {
    kMaxLogSize = static_cast<long>(log_size_m * 1024 * 1024);
    kMaxRotate = rotate;
    NewLogger(file, level);
}

void NewLogger(const char* file, Logger::LogLevel level) {
    kLevel = level;
    FILE* f = fopen(file, "a+");
    setvbuf(f, NULL, _IONBF, 0);
    if (f == NULL) {
        kLogger = NULL;
    }
    else {
        kLogger = new Logger_Simple(f, file);
    }
}

} // namespace
